//
//  SFCDocument.m
//  SVNForCocoa
//
//  Created by Jeremy Pereira on 06/11/2013.
//  Copyright (c) 2013 Jeremy Pereira. All rights reserved.
//

#import "SFCDocument.h"
#import "SFCClient.h"
#import "SFCRevision.h"
#import "SFCDiffViewController.h"
#import "SFCTextFileDiff.h"

@interface SFCStatusEntry (UserInterface)

@property (nonatomic, readonly, copy) NSString* file;
@property (nonatomic, readonly, copy) NSString* nodeStatusString;
@property (nonatomic, readonly, copy) NSString* propertyStatusString;

@end

@implementation SFCStatusEntry(UserInterface)

-(NSString*) nodeStatusString
{
    return [SFCClient stringForStatus: [self nodeStatus]];
}

-(NSString*) propertyStatusString
{
    return [SFCClient stringForStatus: [self propertyStatus]];
}

-(NSString*) file
{
	return [self localAbsolutePath];
}

@end

@interface SFCDocument ()

/*!
 *    @brief Kick off a query to get the status items.
 *
 *    Only run this on the main thread.
 */
-(void) refreshStatusItems;

/*!
 *    @brief Add status items in an array to our list of status items.
 *
 *    This will trigger a UI redraw but it's safe to call it on any thread 
 *    because it checks if the current thread is the main thread, and if it
 *    isn't, it reinvokes itself on the main thread.
 *
 *    @param anArray Array of new status items.
 */
-(void) addStatusItemsFromArray: (NSArray*) anArray;


@property (nonatomic, readonly, strong) SFCClient* svnClient;
@property (strong) NSMutableArray* statusItems;

@property (nonatomic, strong) IBOutlet NSTableView* statusView;
@property (nonatomic, strong) IBOutlet NSOutlineView* sourceList;

+(NSArray*) sourceListItems;

@end

@implementation SFCDocument
{
    NSOperationQueue* svnOperationQueue;
}

@synthesize diffViewController = _diffViewController;

@synthesize sourceList = _sourceList;

@synthesize svnClient = _svnClient;
@synthesize statusItems = _statusItems;
@synthesize statusView = _statusView;

- (id)init
{
    self = [super init];
    if (self != nil)
    {
        _svnClient = [[SFCClient alloc] init];
        svnOperationQueue = [[NSOperationQueue alloc] init];
        [svnOperationQueue setMaxConcurrentOperationCount: 1];
        _statusItems = [[NSMutableArray alloc] init];
    }
    return self;
}

- (NSString *)windowNibName
{
    // Override returning the nib file name of the document
    // If you need to use a subclass of NSWindowController or if your document
    // supports multiple NSWindowControllers, you should remove this method and
    // override -makeWindowControllers instead.
    return @"SFCDocument";
}

- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
    [super windowControllerDidLoadNib:aController];
    // Add any code here that needs to be executed once the windowController has
    // loaded the document's window.
    [self refreshStatusItems];
    [[self sourceList] reloadData];
}

+ (BOOL)autosavesInPlace
{
    return NO;
}

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
    // Insert code here to write your document to data of the specified type. If
    // outError != NULL, ensure that you create and set an appropriate error
    // when returning nil.
    // You can also choose to override -fileWrapperOfType:error:,
    // -writeToURL:ofType:error:, or
    // -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead.
    NSException *exception = [NSException exceptionWithName:@"UnimplementedMethod"
                                                     reason:[NSString stringWithFormat:@"%@ is unimplemented",
                                                             NSStringFromSelector(_cmd)]
                                                   userInfo:nil];
    @throw exception;
    return nil;
}

-(BOOL) readFromURL: (NSURL*) url
             ofType: (NSString*) typeName
              error: (NSError* __autoreleasing *)outError
{

	return YES;
}

-(void) refreshStatusItems
{
    [[self statusItems] removeAllObjects];
    NSBlockOperation* operation
        = [NSBlockOperation blockOperationWithBlock:
           ^{
               SFCRevNum outRevision;
               [[self svnClient] status: [[self fileURL] path]
                               revision: nil
                                  depth: SFC_DEPTH_INFINITY
                                options: 0
                            changeLists: nil
                            statusBlock:
                ^bool (NSString *path, SFCStatusEntry *statusInfo)
                {
                    [self addStatusItemsFromArray: @[statusInfo]];
                    return true;
                }
                         resultRevision: &outRevision
                                  error: nil];
           }];
    [svnOperationQueue addOperation: operation];
    [[self statusView] reloadData];
}

-(void) addStatusItemsFromArray: (NSArray*) anArray
{
    if ([NSThread currentThread] != [NSThread mainThread])
    {
        [self performSelectorOnMainThread: @selector(addStatusItemsFromArray:)
                               withObject: anArray
                            waitUntilDone: NO];
    }
    else
    {
        [[self statusItems] addObjectsFromArray: anArray];
        [[self statusView] reloadData];
    }
}

-(IBAction) refresh: (id)sender
{
	[self refreshStatusItems];
}

#pragma NSTableViewDelegate methods.

-(void) tableViewSelectionDidChange: (NSNotification*) notification
{
	if ([notification object] == [self statusView])
	{
        NSInteger selectedRow = [[self statusView] selectedRow];
        if (selectedRow == -1)
        {
            [[self diffViewController] setDiffs: nil];
        }
        else
        {
            SFCDiffCollection* diffs = [[SFCDiffCollection alloc] init];
            SFCStatusEntry* selectedEntry = [[self statusItems] objectAtIndex: selectedRow];
            NSError* error = nil;
            if ([[self svnClient] diffCmdOptions: nil
                                           path1: [selectedEntry localAbsolutePath]
                                       revision1: [SFCRevision base]
                                           path2:[selectedEntry localAbsolutePath]
                                       revision2: [SFCRevision working]
                                   relativeToDir: nil
                                           depth: SFC_DEPTH_INFINITY
                                         options: 0
                                  headerEncoding: NSUTF8StringEncoding
                                       outStream: [diffs outputStream]
                                     errorStream: nil
                                     changeLists: nil
                                           error: &error])
            {
                [[diffs outputStream] close];
				[[self diffViewController] setDiffs: diffs];
            }
            else
            {
                NSLog(@"Error retrieving diff: %@", error);
                [[self diffViewController] setDiffs: nil];
            }
        }
    }
}

#pragma mark NSTableView datasource methods.

-(NSInteger) numberOfRowsInTableView: (NSTableView*) tableView
{
    return [[self statusItems] count];
}

-(id) tableView: (NSTableView*) tableView
objectValueForTableColumn:(NSTableColumn *)tableColumn
            row:(NSInteger)row
{
    return [[[self statusItems] objectAtIndex: row] valueForKey: [tableColumn identifier]];
}

#pragma mark NSOutlineView datasource methods.

+(NSArray*) sourceListItems
{
    static NSArray* ret = nil;
	static dispatch_once_t onceToken;
    dispatch_once(&onceToken,
    ^{
        ret = @[ NSLocalizedString(@"WORKING COPY", @""),
                 NSLocalizedString(@"BRANCHES", @""),
                 NSLocalizedString(@"TAGS", @"")];
    });
    return ret;
}

-(id) outlineView: (NSOutlineView*) outlineView
            child: (NSInteger) index
           ofItem: (id) item
{
    id ret = nil;
    if (item == nil)
    {
		ret = [[[self class] sourceListItems] objectAtIndex: index];
    }
    return ret;
}

-(BOOL) outlineView: (NSOutlineView*) outlineView isItemExpandable: (id) item
{
    return item == nil;
}

-(NSInteger) outlineView:(NSOutlineView *)outlineView
  numberOfChildrenOfItem:(id)item
{
	return item == nil ? [[[self class] sourceListItems] count] : 0;
}

-(id) outlineView: (NSOutlineView*) outlineView
objectValueForTableColumn: (NSTableColumn*) tableColumn
           byItem:(id)item
{
    return item;
}

#pragma mark Outline view delegate

- (NSView *)outlineView: (NSOutlineView*) outlineView
     viewForTableColumn: (NSTableColumn*) tableColumn
                   item:(id)item
{
    NSTableCellView* ret = nil;

    if ([[[self class] sourceListItems] containsObject: item])
    {
        ret = [outlineView makeViewWithIdentifier:@"HeaderCell" owner:self];
        [[ret textField] setStringValue: [self outlineView: outlineView
                     			 objectValueForTableColumn: tableColumn
                                 			        byItem: item]];
    }
    else
    {
        ret = [outlineView makeViewWithIdentifier:@"DataCell" owner:self];
    }

    return ret;
}

@end
